Skip to content

fix(installation-cleanup): don't mark live agents stale (Telegram bridge was 403ing)#696

Merged
lilyshen0722 merged 1 commit into
Team-Commonly:mainfrom
alptekinkekilli:fix/installation-staleness-liveness-pr
Jul 21, 2026
Merged

fix(installation-cleanup): don't mark live agents stale (Telegram bridge was 403ing)#696
lilyshen0722 merged 1 commit into
Team-Commonly:mainfrom
alptekinkekilli:fix/installation-staleness-liveness-pr

Conversation

@alptekinkekilli

Copy link
Copy Markdown
Contributor

The nightly cleanup cron marked a live, actively-relaying agent installation as stale, which made every request from it fail with 403 Agent token not authorized for this pod (pod authorization requires status: 'active').

Hit in production on 2026-07-19: the Telegram bridge (telegram-app) went silent. yunus had gone the same way the day before — both ends of the bridge decaying one after the other. Related: APP-47, filed 2026-07-08, which predicted this recurrence.

Root cause

markStaleInstallations treats two signals as "dead", and both misread a send-only integration:

  1. No unexpired token. These are long-lived file-based tokens with no expiresAt; hasValidRuntimeToken deliberately treats null-expiry as invalid. That's right for abandoned stragglers, but wrong for tokens that are intentionally permanent.
  2. No AgentEvent for 7 days. Posting a pod message emits no AgentEvent at all (measured: telegram-app event count was 0). A send-only integration looks dead no matter how much traffic it pushes. Other agents survive only because they emit heartbeat events.

Both "dead" signals fired for an agent that was demonstrably alive.

Fix

Use agentRuntimeTokens[].lastUsedAt as the liveness signal — agentRuntimeAuth writes it on every authenticated request. The service already loads that user document, so this costs no extra query; the answer was in hand and simply not read.

Second bug found while fixing this

There was no age floor for new installations. A freshly provisioned agent reads "dead" on both signals purely because nobody has @-mentioned it yet — its token has never been used and it has emitted no events. That is a normal state: departments get provisioned before work is assigned, and lazily-started daemons don't run until mentioned.

Observed: dev-jr, installed that afternoon, was marked stale minutes later. pruneStaleInstallations would have deleted it 14 days on. Added createdAt < cutoff.

Verification

  • 5 unit tests — the first tests for this service. Temporarily reverting the fix turns the outage test red, so the test genuinely guards the behavior rather than passing vacuously.
  • Ran the real sweeper against live data: marked=1 before the age floor (it wrongly caught dev-jr), marked=0 after.
  • telegram-app + yunus live POST: HTTP 200.
  • Rebased onto current main (6 commits ahead of where this was authored); tests still green.

Note on scope

APP-47 also suggests having CLI agent attach write an expiresAt. That is not done here — if permanent tokens are an intentional choice, that item should be dropped rather than implemented. This PR fixes the sweeper's reading of liveness, not the token format.

🤖 Generated with Claude Code

…m köprüsü 403'ledi)

Telegram köprüsü bugün her gönderimde 403 döndürdü:
"Agent token not authorized for this pod". Webhook kapanmamıştı, agent de
düşmemişti — gecelik 04:00 cron'u telegram-app kurulumunu 'stale' işaretlemişti
ve pod yetkilendirmesi status 'active' şartı arıyor. yunus bir gün önce aynı
şekilde düşmüştü; köprünün iki ucu peş peşe çürüyordu.

markStaleInstallations iki "ölü" sinyaline bakıyor ve köprüde İKİSİ DE yanlış
okuyor:

  1. Süresi geçmemiş token yok. Köprünün token'ı kalıcı bir dosya token'ı
     (~/.commonly/tokens/<ad>.json) ve expiresAt taşımıyor;
     hasValidRuntimeToken boş expiry'yi kasten geçersiz sayıyor. Bu, terk
     edilmiş artıklar için doğru ama kalıcı token'ları yanlış damgalıyor.
  2. 7 gündür AgentEvent yok. Pod'a mesaj göndermek HİÇ AgentEvent üretmiyor
     (ölçüldü: telegram-app event sayısı 0). Gönderim-yönlü bir entegrasyon
     ne kadar trafik basarsa bassın ölü görünüyor. Diğer agent'lar yalnızca
     heartbeat yaydıkları için hayatta kalıyor.

Dürüst canlılık sinyali agentRuntimeTokens[].lastUsedAt: agentRuntimeAuth onu
HER kimlik doğrulanmış istekte yazıyor. Servis o user dökümanını zaten
yüklüyordu — cevabı elinde tutup okumuyordu, dolayısıyla ek sorgu maliyeti yok.

İkinci hata, bunu ararken çıktı ve daha geniş: YENİ KURULUMLAR İÇİN YAŞ TABANI
YOKTU. dev-jr 14:31'de kuruldu, dakikalar sonra stale işaretlendi — token'ı
henüz kullanılmamış, event'i yok, çünkü kimse daha @-mention atmamıştı. Bu
normal bir durum: departmanlar iş verilmeden önce provision ediliyor ve tembel
başlayan daemon'lar mention'a kadar koşmuyor. pruneStaleInstallations 14 gün
sonra bunları SİLECEKTİ. createdAt < cutoff tabanı eklendi.

Doğrulama:
- 5 birim testi (bu servisin ilk testleri). Düzeltme geçici kaldırılınca
  outage testi kırmızıya döndü — test boş değil, gerçekten koruyor.
- Gerçek veriye karşı sweeper koşusu: düzeltmeden önce marked=1 (dev-jr'ı
  yanlış yakaladı), sonra marked=0.
- telegram-app + yunus canlı POST: HTTP 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent diagnosis. Verified the blind-spot claim against the code: hasValidRuntimeToken's null-expiry-is-invalid stance is right for abandoned stragglers but wrong for long-lived file tokens, and message posting indeed emits no AgentEvent — so a send-only integration trips both 'dead' signals while actively relaying. lastUsedAt is the honest signal and it's already on the loaded user doc, so the fix is free. The regression test covering the exact outage case (null expiry + recent use) is exactly what this cron needed. This also protects every BYO wrapper agent using file tokens. Fourth quality PR in a row — thank you, and sorry the cron ate your bridge.

@lilyshen0722
lilyshen0722 merged commit 908ed04 into Team-Commonly:main Jul 21, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants